package com.ruoyi.framework.config.redis;

import com.alibaba.fastjson2.support.spring.data.redis.GenericFastJsonRedisSerializer;
import io.lettuce.core.resource.NettyCustomizer;
import io.netty.channel.Channel;
import io.netty.channel.ChannelDuplexHandler;
import io.netty.channel.ChannelHandlerContext;
import io.netty.handler.timeout.IdleStateEvent;
import io.netty.handler.timeout.IdleStateHandler;
import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.autoconfigure.cache.CacheProperties;
import org.springframework.boot.autoconfigure.data.redis.ClientResourcesBuilderCustomizer;
import org.springframework.cache.annotation.EnableCaching;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.cache.RedisCacheConfiguration;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.data.redis.serializer.RedisSerializationContext;
import org.springframework.data.redis.serializer.StringRedisSerializer;

import java.time.Duration;
import java.util.concurrent.TimeUnit;

/**
 * redis配置
 *
 * @author ruoyi
 */
@Slf4j
@Configuration
@EnableCaching
public class RedisConfiguration {

    @Bean(value = "defaultRedisTemplate")
    public RedisTemplate<Object, Object> redisTemplate(RedisConnectionFactory connectionFactory) {
        RedisTemplate<Object, Object> template = new RedisTemplate<>();
        template.setConnectionFactory(connectionFactory);
        GenericFastJsonRedisSerializer genericFastJsonRedisSerializer = new GenericFastJsonRedisSerializer();
        // 使用StringRedisSerializer来序列化和反序列化redis的key值
        template.setKeySerializer(new StringRedisSerializer());
        template.setValueSerializer(genericFastJsonRedisSerializer);
        // Hash的key也采用StringRedisSerializer的序列化方式
        template.setHashKeySerializer(new StringRedisSerializer());
        template.setHashValueSerializer(genericFastJsonRedisSerializer);
        template.afterPropertiesSet();
        return template;
    }


    @Bean
    public DefaultRedisScript<Long> limitScript() {
        DefaultRedisScript<Long> redisScript = new DefaultRedisScript<>();
        redisScript.setScriptText(limitScriptText());
        redisScript.setResultType(Long.class);
        return redisScript;
    }

    /**
     * 限流脚本
     */
    private String limitScriptText() {
        return "local key = KEYS[1]\n" +
                "local count = tonumber(ARGV[1])\n" +
                "local time = tonumber(ARGV[2])\n" +
                "local current = redis.call('get', key);\n" +
                "if current and tonumber(current) > count then\n" +
                "    return tonumber(current);\n" +
                "end\n" +
                "current = redis.call('incr', key)\n" +
                "if tonumber(current) == 1 then\n" +
                "    redis.call('expire', key, time)\n" +
                "end\n" +
                "return tonumber(current);";
    }


    @Bean
    public RedisCacheConfiguration redisCacheConfiguration(CacheProperties cacheProperties) {
        log.info("开始配置Spring Redis Cache Configuration To Spring Redis CacheManager");
        CacheProperties.Redis redisProperties = cacheProperties.getRedis();
        RedisCacheConfiguration config = RedisCacheConfiguration.defaultCacheConfig();
        RedisSerializationContext.SerializationPair<Object> valueSerializationPair = RedisSerializationContext
                .SerializationPair.fromSerializer(new GenericFastJsonRedisSerializer());
        config = config.serializeValuesWith(valueSerializationPair);
        Duration timeToLive = redisProperties.getTimeToLive();
        if (timeToLive != null) {
            config = config.entryTtl(timeToLive);
        }
        String keyPrefix = redisProperties.getKeyPrefix();
        if (keyPrefix != null) {
            config = config.computePrefixWith(name -> keyPrefix + name + ":");
        } else {
            config = config.computePrefixWith(name -> name + ":");
        }
        boolean cacheNullValues = redisProperties.isCacheNullValues();
        if (!cacheNullValues) {
            config = config.disableCachingNullValues();
        }
        boolean useKeyPrefix = redisProperties.isUseKeyPrefix();
        if (!useKeyPrefix) {
            config = config.disableKeyPrefix();
        }
        return config;
    }


    @Bean
    public ClientResourcesBuilderCustomizer clientResourcesBuilderCustomizer() {
        return clientResourcesBuilder -> {
            NettyCustomizer nettyCustomizer = new NettyCustomizer() {
                @Override
                public void afterChannelInitialized(Channel channel) {
                    channel.pipeline().addLast(new IdleStateHandler(60, 120, 180, TimeUnit.SECONDS));
                    channel.pipeline().addLast(new ChannelDuplexHandler() {
                        @Override
                        public void userEventTriggered(ChannelHandlerContext ctx, Object evt) {
                            if (evt instanceof IdleStateEvent) {
                                log.info("检测到空闲的连接事件:{}", evt);
                                ctx.disconnect();
                            }
                        }
                    });
                }
            };
            clientResourcesBuilder.nettyCustomizer(nettyCustomizer);
        };
    }
}
